﻿using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using CompactMapper.Orm.Contracts;
using CompactMapper.Orm.Extensions;
using CompactMapper.Orm.Queries;
using CompactMapper.Orm.UnitOfWork.Commands.Implementations;

namespace CompactMapper.Orm.UnitOfWork.Mappings
{
    internal class QueryMappings : CommandMappingsBase
    {
        internal QueryMappings(TablesTree mappingsTree)
            : base(mappingsTree)
        {
        }

        protected override void CreateSpecificMappings(Tree<Table> tablesTree)
        {
            foreach (var child in tablesTree.Children)
            {
                child.Node.QueryCommand = new QueryCommand(child.Node);
                child.Node.GetByKeyCommand = new GetByKeyCommand(child.Node);
                CreateSpecificMappings(child);
            }
        }

        public IEnumerable<T> Run<T>(SQLiteConnection connection, Query query) where T : class
        {
            var mapping = TablesTree.GetTreeFor(typeof(T));
            return RunCommandsAtTopLevel<T>(mapping, connection, query, TablesTree).OfType<T>();
        }


        private static IEnumerable<T> RunCommandsAtTopLevel<T>(Tree<Table> tablesTree,
                                                                      SQLiteConnection connection, Query query,
                                                                      TablesTree tablesTreeRoot)
            where T : class
        {
            var linesReader = new LinesReader(connection, tablesTree.Node, query, tablesTreeRoot);
            var allAtThisLevel = linesReader.Cast<T>().ToArray();


            if (allAtThisLevel.HasNoItems() || !(allAtThisLevel.First() is IKeyedPersistent))
                return allAtThisLevel;


            var thisTable = tablesTree.Node;
            var thisTableName = thisTable.Name;

            foreach (IKeyedPersistent topLevel in allAtThisLevel)
            {
                foreach (var childtablesTree in tablesTree.Children)
                {
                    var parentKey = topLevel.Key;

                    var childTable = childtablesTree.Node;
                    var relation = childTable.GetRelationshipWith(thisTableName);
                    if (relation != Relationship.None)
                    {
                        var allAtNextLevelForParent = RunCommandsIndirectRelation(childtablesTree, connection,
                                                                                  thisTableName, parentKey);
                        if (allAtNextLevelForParent.HasItems())
                        {
                            if (relation.IsMultiple)
                            {
                                var addMethod = thisTable.Meta.GetAddMethodByTableName(childTable.Name);
                                foreach (IKeyedPersistent childLevel in allAtNextLevelForParent)
                                {
                                    addMethod.Invoke(topLevel,
                                                     new object[] { childLevel });
                                }
                            }
                            else
                            {
                                thisTable.Meta.RelationsByTableName[childTable.Name].
                                    SetValue(topLevel, allAtNextLevelForParent.Single());
                            }
                        }
                    }
                }
            }
            return allAtThisLevel;
        }


        private static IEnumerable<IKeyedPersistent> RunCommandsIndirectRelation(Tree<Table> tablesTree,
                                                                            SQLiteConnection connection,
                                                                            String parentTableName, IKey parentTableKey
                                                                            )
        {
            IEnumerable<IKeyedPersistent> allAtThisLevel;
            using (var linesReader = new LinesReader(connection, tablesTree.Node, parentTableName, parentTableKey))
                allAtThisLevel = linesReader.Cast<IKeyedPersistent>().ToArray();
            foreach (var topLevel in allAtThisLevel)
            {
                var theKey = topLevel.Key;

                var thisTable = tablesTree.Node;
                var thisTableName = thisTable.Name;


                foreach (var childtablesTree in tablesTree.Children)
                {
                    var childTable = childtablesTree.Node;

                    var relationship = childTable.GetRelationshipWith(thisTableName);
                    if (relationship != Relationship.None)
                    {
                        var allAtNextLevelForParent = RunCommandsIndirectRelation(childtablesTree, connection,
                                                                                  thisTableName, theKey);
                        if (allAtNextLevelForParent.HasItems())
                        {
                            if (relationship.IsMultiple)
                            {
                                var addMethod = thisTable.Meta.GetAddMethodByTableName(childTable.Name);
                                foreach (var childLevel in allAtNextLevelForParent)
                                {
                                    addMethod.Invoke(topLevel,
                                                     new object[] { childLevel });
                                }
                            }
                            else
                            {
                                thisTable.Meta.RelationProperties[childTable.Name].
                                    SetValue(topLevel, allAtNextLevelForParent.Single());
                            }
                        }
                    }
                }
            }

            return allAtThisLevel;
        }
    }
}